import { Parser } from './Parser.js';
import { Operator } from './Operator.js';
import { Renderer } from './Renderer.js';
import { Observer } from './Observer.js';

/** @类 逻辑类 */
export class Logic {
  constructor(el) {
    this.parser = new Parser();
    this.observer = new Observer();
    this.renderer = new Renderer({ el, observer: this.observer });
    this.operator = new Operator({
      parser: this.parser,
      observer: this.observer,
      renderer: this.renderer,
    });
    this.drawingDone = null; // 绘图结束回调
    this.drawingPoint = null; // 绘图中的移动记录点
    this.observer.addListener('draw', this.onDraw.bind(this));
    this.observer.addListener('zoom', this.onZoom.bind(this));
    this.observer.addListener('drag', this.onDrag.bind(this));
    this.observer.addListener('click', this.onClick.bind(this));
    this.observer.addListener('resize', this.onResize.bind(this));
    this.observer.addListener('cancel', this.onCancel.bind(this));
    this.observer.addListener('pointmove', this.onPointmove.bind(this));
    this.observer.addListener('pointdown', this.onPointdown.bind(this));
  }

  /** @方法 加载项目 */
  load(data) {
    this.parser.load({
      data,
      observer: this.observer,
      renderer: this.renderer,
    });
  }

  /** @方法 开启绘图模式 */
  drawStart(done) {
    this.operator.isDrawing = true;
    this.drawingPoint = { x: -10, y: -10 };
    this.drawingDone = done;
    this.parser.selected(this.parser.stages.length - 1);
  }

  /** @方法 结束绘图模式 */
  drawEnd() {
    const { stages } = this.parser;
    const current = stages[stages.length - 1];
    this.operator.isDrawing = false;
    this.drawingDone && this.drawingDone(current.polygon.length > 0);
    this.drawingPoint = null;
    this.drawingDone = null;
  }

  /** @方法 销毁内容 */
  destroy() {
    this.renderer.destroy();
    this.observer.destroy();
    this.operator.destroy();
  }

  /** @事件 渲染画面 */
  onDraw() {
    const { parser, renderer } = this;
    const { canvas, buffer, bufferCtx, canvasCtx } = renderer;
    const { background, scale, stages, activeIndex } = parser;
    const scaling = scale.scaling * scale.rate; // 其中 scale.rate = 画布宽度 / 背景宽度
    // 绘制背景图 to 缓存区
    background.image && bufferCtx.drawImage(
      background.image,
      0,
      0,
      buffer.width,
      buffer.height,
    );
    // 绘制点击区 to 缓存区
    stages.forEach((stage, index) => {
      this.onDrawStage({ stage, bufferCtx, active: index === activeIndex });
    });
    // 绘制缓存区 to 画板区
    buffer.width > 0 && buffer.height > 0 && canvasCtx.drawImage(
      buffer,
      background.offsetX / scaling, // 源图像的 x 坐标
      background.offsetY / scaling, // 源图像的 y 坐标
      canvas.width / scaling, // 源图像的宽度
      canvas.height / scaling, // 源图像的高度
      0, // 目标图像的 x 坐标
      0, // 目标图像的 y 坐标
      canvas.width, // 目标图像的宽度
      canvas.height // 目标图像的高度
    ); 
  }

  /** @事件 绘制背景 */
  onDrawStage({
    stage,
    bufferCtx,
    active = false,
  }) {
    const colors = this.GetColors(stage.color);
    // 绘制面
    if (stage.polygon.length > 0) {
      bufferCtx.fillStyle = this.drawingPoint === null && active ? colors.active : colors.fill;
      bufferCtx.beginPath();
      bufferCtx.moveTo(stage.polygon[0].x, stage.polygon[0].y);
      stage.polygon.forEach(({ x, y }) => bufferCtx.lineTo(x, y));
      bufferCtx.closePath();
      bufferCtx.fill();
    }
    // 绘制线
    if (stage.polygon.length > 0) {
      bufferCtx.strokeStyle = colors.line;
      bufferCtx.lineWidth = 2;
      bufferCtx.beginPath();
      bufferCtx.moveTo(stage.polygon[0].x, stage.polygon[0].y);
      stage.polygon.forEach(({ x, y }) => bufferCtx.lineTo(x, y));
      this.drawingPoint && active && bufferCtx.lineTo(this.drawingPoint.x, this.drawingPoint.y);
      bufferCtx.closePath();
      bufferCtx.stroke();
    }
    // 绘制点
    (this.drawingPoint && active ? [...stage.polygon, this.drawingPoint] : stage.polygon).forEach(({ x, y }) => {
      bufferCtx.fillStyle = colors.dots;
      bufferCtx.beginPath();
      bufferCtx.arc(x, y, 2, 0, 2 * Math.PI);
      bufferCtx.closePath();
      bufferCtx.fill();
    });
  }

  /** @事件 尺寸变化 */
  onResize() {
    const { parser, renderer } = this;
    const { background, scale } = parser;
    scale.rate = renderer.width / background.width; // 更新缩放率
  }

  /** @事件 取消绘制 */
  onCancel() {
    const { stages } = this.parser;
    const current = stages[stages.length - 1];
    if (current.polygon.length === 0) {
      // 绘制结束
      this.drawEnd();
    } else {
      // 移除记录点
      current.polygon.splice(current.polygon.length - 1, 1);
    }
  }

  /** @事件 触发绘制移动 */
  onPointmove({ point }) {
    const { scale, background } = this.parser;
    const scaling = scale.scaling * scale.rate;
    this.drawingPoint = {
      x: (point.x + background.offsetX) / scaling,
      y: (point.y + background.offsetY) / scaling,
    };
  }

  /** @事件 触发绘制按下 */
  onPointdown({ point }) {
    const { scale, stages, background } = this.parser;
    const scaling = scale.scaling * scale.rate;
    const current = stages[stages.length - 1];
    const newPoint = {
      x: (point.x + background.offsetX) / scaling,
      y: (point.y + background.offsetY) / scaling,
    };
    if (current.polygon.length > 0) {
      const lastX = Math.abs(newPoint.x - current.polygon[0].x);
      const lastY = Math.abs(newPoint.y - current.polygon[0].y);
      if (lastX < 5 && lastY < 5 && current.polygon.length > 2) {
        // 绘制结束
        this.drawEnd();
      } else {
        // 加入记录点
        current.polygon.push(newPoint);
      }
    } else {
      // 加入记录点
      current.polygon.push(newPoint);
    }
  }

  /** @事件 触发拖动 */
  onDrag({ point, downPoint, downOffset }) {
    const { scale, background } = this.parser;

    // 计算偏移位置
    const newOffset = this.GetLimitOffset({
      x: downOffset.x + (downPoint.x - point.x),
      y: downOffset.y + (downPoint.y - point.y),
      scaling: scale.scaling * scale.rate,
    });

    // 更新偏移位置
    background.offsetX = newOffset.x;
    background.offsetY = newOffset.y;
  }

  /** @事件 放大缩小 */
  onZoom({ zoom }) {
    const { parser, renderer } = this;
    const { scale, background } = parser;
    let newScaling;
    // 计算缩放比例
    if (zoom) {
      newScaling = scale.scaling + scale.step;
    } else {
      newScaling = scale.scaling - scale.step;
    }
    // 限制缩放比例
    newScaling = Math.max(scale.min, Math.min(newScaling, scale.max));
    // 计算缩放前后画布中心点在地图上的位置
    const scalingBefore = scale.scaling * scale.rate;
    const centerBefore = {
      x: (background.offsetX + renderer.width / 2) / scalingBefore,
      y: (background.offsetY + renderer.height / 2) / scalingBefore,
    };
    const scalingAfter = newScaling * scale.rate;
    const centerAfter = {
      x: (background.offsetX + renderer.width / 2) / scalingAfter,
      y: (background.offsetY + renderer.height / 2) / scalingAfter,
    };
    // 计算偏移位置
    const newOffset = this.GetLimitOffset({
      x: background.offsetX + (centerBefore.x - centerAfter.x) * scalingAfter,
      y: background.offsetY + (centerBefore.y - centerAfter.y) * scalingAfter,
      scaling: scalingAfter,
    });
    // 更新缩放比例
    scale.scaling = newScaling;
    // 更新偏移位置
    background.offsetX = newOffset.x;
    background.offsetY = newOffset.y;
  }

  /** @事件 点击触发 */
  onClick({ point }) {
    const { parser } = this;
    const { scale, background } = parser;
    const scaling = scale.scaling * scale.rate;
    this.observer.dispatch('selected', parser.clickStage({
      x: (point.x + background.offsetX) / scaling,
      y: (point.y + background.offsetY) / scaling,
    }));
  }

  /** @辅助 限制不能超出画布 */
  GetLimitOffset({ x, y, scaling }) {
    const { canvas } = this.renderer;
    const { background } = this.parser;
    const max = {
      x: Math.max(0, background.width * scaling - canvas.width),
      y: Math.max(0, background.height * scaling - canvas.height)
    };
    return {
      x: Math.min(Math.max(x, 0), max.x),
      y: Math.min(Math.max(y, 0), max.y),
    };
  }

  /** @辅助 获取颜色 */
  GetColors(color) {
    color = color.substr(4);
    color = color.substr(0, color.length - 1);
    return {
      dots: `rgba(${color}, 1)`,
      line: `rgba(${color}, 0.8)`,
      fill: `rgba(${color}, 0.2)`,
      active: `rgba(255, 255, 255, 0.5)`,
    }
  }
}